﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Stat;
using NHibernate.Type;

namespace Framework.Repository
{
    public sealed class Repository<T> : IRepository<T>
        where T : class
    {
        [ExcludeFromCodeCoverage]
        class StateLessSessionAdaper : ISession
        {
            private readonly IStatelessSession statelessSession;

            public StateLessSessionAdaper(IStatelessSession statelessSession)
            {
                this.statelessSession = statelessSession;
            }

            public void Dispose()
            {
                statelessSession.Dispose();
            }

            public ISessionImplementor GetSessionImplementation()
            {
                return statelessSession.GetSessionImplementation();
            }

            public IMultiCriteria CreateMultiCriteria()
            {
                throw new NotImplementedException();
            }

            public ISession GetSession(EntityMode entityMode)
            {
                throw new NotImplementedException();
            }

            public EntityMode ActiveEntityMode { get { throw new NotImplementedException(); } }
            public FlushMode FlushMode { get; set; }
            public CacheMode CacheMode { get; set; }

            public void Flush()
            {
                throw new NotImplementedException();
            }

            public IDbConnection Disconnect()
            {
                throw new NotImplementedException();
            }

            public void Reconnect()
            {
                throw new NotImplementedException();
            }

            public void Reconnect(IDbConnection connection)
            {
                throw new NotImplementedException();
            }

            IDbConnection ISession.Close()
            {
                throw new NotImplementedException();
            }

            public void CancelQuery()
            {
                throw new NotImplementedException();
            }

            public bool IsDirty()
            {
                throw new NotImplementedException();
            }

            public bool IsReadOnly(object entityOrProxy)
            {
                throw new NotImplementedException();
            }

            public void SetReadOnly(object entityOrProxy, bool readOnly)
            {
                throw new NotImplementedException();
            }

            public object GetIdentifier(object obj)
            {
                throw new NotImplementedException();
            }

            public bool Contains(object obj)
            {
                throw new NotImplementedException();
            }

            public void Evict(object obj)
            {
                throw new NotImplementedException();
            }

            public object Load(Type theType, object id, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public object Load(string entityName, object id, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public object Load(Type theType, object id)
            {
                throw new NotImplementedException();
            }

            public T1 Load<T1>(object id, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public T1 Load<T1>(object id)
            {
                throw new NotImplementedException();
            }

            public object Load(string entityName, object id)
            {
                throw new NotImplementedException();
            }

            public void Load(object obj, object id)
            {
                throw new NotImplementedException();
            }

            public void Replicate(object obj, ReplicationMode replicationMode)
            {
                throw new NotImplementedException();
            }

            public void Replicate(string entityName, object obj, ReplicationMode replicationMode)
            {
                throw new NotImplementedException();
            }

            public object Save(object obj)
            {
                throw new NotImplementedException();
            }

            public void Save(object obj, object id)
            {
                throw new NotImplementedException();
            }

            public object Save(string entityName, object obj)
            {
                throw new NotImplementedException();
            }

            public void SaveOrUpdate(object obj)
            {
                throw new NotImplementedException();
            }

            public void SaveOrUpdate(string entityName, object obj)
            {
                throw new NotImplementedException();
            }

            public void Update(object entity)
            {
                statelessSession.Update(entity);
            }

            public void Update(object obj, object id)
            {
                throw new NotImplementedException();
            }

            public void Update(string entityName, object entity)
            {
                statelessSession.Update(entityName, entity);
            }

            public object Merge(object obj)
            {
                throw new NotImplementedException();
            }

            public object Merge(string entityName, object obj)
            {
                throw new NotImplementedException();
            }

            public T1 Merge<T1>(T1 entity) where T1 : class
            {
                throw new NotImplementedException();
            }

            public T1 Merge<T1>(string entityName, T1 entity) where T1 : class
            {
                throw new NotImplementedException();
            }

            public void Persist(object obj)
            {
                throw new NotImplementedException();
            }

            public void Persist(string entityName, object obj)
            {
                throw new NotImplementedException();
            }

            public object SaveOrUpdateCopy(object obj)
            {
                throw new NotImplementedException();
            }

            public object SaveOrUpdateCopy(object obj, object id)
            {
                throw new NotImplementedException();
            }

            public void Delete(object entity)
            {
                statelessSession.Delete(entity);
            }

            public void Delete(string entityName, object entity)
            {
                statelessSession.Delete(entityName, entity);
            }

            public int Delete(string query)
            {
                throw new NotImplementedException();
            }

            public int Delete(string query, object value, IType type)
            {
                throw new NotImplementedException();
            }

            public int Delete(string query, object[] values, IType[] types)
            {
                throw new NotImplementedException();
            }

            public void Lock(object obj, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public void Lock(string entityName, object obj, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public object Get(Type clazz, object id, LockMode lockMode)
            {
                throw new NotImplementedException();
            }

            public object Get(string entityName, object id)
            {
                return statelessSession.Get(entityName, id);
            }

            public T1 Get<T1>(object id)
            {
                return statelessSession.Get<T1>(id);
            }

            public T1 Get<T1>(object id, LockMode lockMode)
            {
                return statelessSession.Get<T1>(id, lockMode);
            }

            public string GetEntityName(object obj)
            {
                throw new NotImplementedException();
            }

            public IFilter EnableFilter(string filterName)
            {
                throw new NotImplementedException();
            }

            public IFilter GetEnabledFilter(string filterName)
            {
                throw new NotImplementedException();
            }

            public void DisableFilter(string filterName)
            {
                throw new NotImplementedException();
            }

            public IMultiQuery CreateMultiQuery()
            {
                throw new NotImplementedException();
            }

            public void Refresh(object entity)
            {
                statelessSession.Refresh(entity);
            }

            public void Refresh(object entity, LockMode lockMode)
            {
                statelessSession.Refresh(entity, lockMode);
            }

            public LockMode GetCurrentLockMode(object obj)
            {
                throw new NotImplementedException();
            }

            public IQueryOver<T1, T1> QueryOver<T1>(string entityName, Expression<Func<T1>> alias) where T1 : class
            {
                throw new NotImplementedException();
            }

            public IQuery CreateQuery(string queryString)
            {
                return statelessSession.CreateQuery(queryString);
            }

            public IQuery CreateFilter(object collection, string queryString)
            {
                throw new NotImplementedException();
            }

            public IQuery GetNamedQuery(string queryName)
            {
                return statelessSession.GetNamedQuery(queryName);
            }

            public ICriteria CreateCriteria<T1>() where T1 : class
            {
                return statelessSession.CreateCriteria<T1>();
            }

            public ICriteria CreateCriteria<T1>(string alias) where T1 : class
            {
                return statelessSession.CreateCriteria<T1>(alias);
            }

            public ICriteria CreateCriteria(Type entityType)
            {
                return statelessSession.CreateCriteria(entityType);
            }

            public ICriteria CreateCriteria(Type entityType, string alias)
            {
                return statelessSession.CreateCriteria(entityType, alias);
            }

            public ICriteria CreateCriteria(string entityName)
            {
                return statelessSession.CreateCriteria(entityName);
            }

            public ICriteria CreateCriteria(string entityName, string alias)
            {
                return statelessSession.CreateCriteria(entityName, alias);
            }

            public IQueryOver<T1, T1> QueryOver<T1>() where T1 : class
            {
                return statelessSession.QueryOver<T1>();
            }

            public IQueryOver<T1, T1> QueryOver<T1>(Expression<Func<T1>> alias) where T1 : class
            {
                return statelessSession.QueryOver(alias);
            }

            public IQueryOver<T1, T1> QueryOver<T1>(string entityName) where T1 : class
            {
                throw new NotImplementedException();
            }

            public ISQLQuery CreateSQLQuery(string queryString)
            {
                return statelessSession.CreateSQLQuery(queryString);
            }

            public void Clear()
            {
                throw new NotImplementedException();
            }

            public object Get(Type clazz, object id)
            {
                throw new NotImplementedException();
            }

            public ITransaction BeginTransaction()
            {
                return statelessSession.BeginTransaction();
            }

            public ITransaction BeginTransaction(IsolationLevel isolationLevel)
            {
                return statelessSession.BeginTransaction(isolationLevel);
            }

            public ISession SetBatchSize(int batchSize)
            {
                statelessSession.SetBatchSize(batchSize);
                return this;
            }

            public ISessionFactory SessionFactory { get { throw new NotImplementedException(); } }

            public IDbConnection Connection
            {
                get { return statelessSession.Connection; }
            }

            public ITransaction Transaction
            {
                get { return statelessSession.Transaction; }
            }

            public ISessionStatistics Statistics { get { throw new NotImplementedException(); } }

            public bool IsOpen
            {
                get { return statelessSession.IsOpen; }
            }

            public bool IsConnected
            {
                get { return statelessSession.IsConnected; }
            }

            public bool DefaultReadOnly { get; set; }
        }

        private readonly ISessionFactory sessionFactory;

        public Repository(ISessionFactory sessionFactory)
        {
            this.sessionFactory = sessionFactory;
        }

        public IEnumerable<T> ToList()
        {
            return Query(s => s.QueryOver<T>().List<T>());
        }

        public IEnumerable<T> Where(Expression<Func<T, bool>> filter)
        {
            return Query(s => s.QueryOver<T>().Where(filter).List<T>());
        }

        public void Save(params T[] items)
        {
            Update(s => items.ForEach(s.SaveOrUpdate));
        }

        public void Delete(params T[] items)
        {
            Update(s => items.ForEach(s.Delete));
        }

        public void Delete(Expression<Func<T, bool>> filter)
        {
            Update(s => s.QueryOver<T>().Where(filter).List<T>().ForEach(s.Delete));
        }

        private IEnumerable<T> Query(Func<ISession, IEnumerable<T>> query)
        {
            return WithRethrowing(() =>
            {
                using (var session = new StateLessSessionAdaper(sessionFactory.OpenStatelessSession()))
                {
                    return query(session);
                }
            });
        }

        private void Update(Action<ISession> action)
        {
            DoWithRethrowing(() =>
            {
                using (var session = sessionFactory.OpenSession())
                {
                    using (var transaction = session.BeginTransaction())
                    {
                        try
                        {
                            action(session);
                            transaction.Commit();
                        }
                        catch
                        {
                            transaction.Rollback();
                            throw;
                        }
                    }
                }
            });
        }

        private static void DoWithRethrowing(Action action)
        {
            WithRethrowing(() =>
            {
                action();
                return null;
            });
        }

        private static IEnumerable<T> WithRethrowing(Func<IEnumerable<T>> func)
        {
            try
            {
                return func();
            }
            catch (Exception e)
            {
                throw new RepositoryException("error during database operation, see inner innerException details", e);
            }
        }
    }
}